package com.googlecode.skald.client;

import java.util.logging.Level;
import java.util.logging.Logger;

import com.google.gwt.core.client.GWT;
import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestBuilder.Method;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.http.client.Response;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.ui.HTML;
import com.googlecode.skald.client.affordances.InvalidFormatException;
import com.googlecode.skald.client.comm.ResponseFooter;

/**
 * Represents the game server to which Skald should connect and communicate.
 * 
 * @author Zach Tomaszewski
 */
public class Server implements RequestCallback {

  public static final String URL = GWT.getModuleBaseURL();
  public static final String INIT_URL = URL + "init";
  public static final String CMD_URL = URL + "cmd";
  private static final Logger logger = Logger.getLogger(Server.class.getName());
  
  /**
   * Logs any error that resulted and alerts user via status bar.
   */
  public void onError(Request request, Throwable e) {
    String mesg = "Did not receive a (valid) response from server.";
    Skald.gui.setStatusError(mesg);
    logger.log(Level.WARNING, mesg, e);
  }  
  
  /**
   * Logs any non-200 response or else handles the response.
   * Clears the status bar if response was valid. 
   */
  public void onResponseReceived(Request request, Response response) {
    if (200 == response.getStatusCode()) {
      Skald.gui.clearStatus();
      handleResponse(response.getText());
    } else {
      String mesg = "Server gave a " + response.getStatusCode() + " response";
      Skald.gui.setStatusError(mesg + ".");
      logger.log(Level.SEVERE, mesg + ": " + response.getStatusText());
      logger.log(Level.FINE, "Headers: " + response.getHeadersAsString());
      logger.log(Level.FINE, "Text: " + response.getText());
    }
  }
  
  /**
   * Processes the given response text.  This generally means updating the 
   * GUI with the reply from the server.
   * <p>
   * All Skald server response text should have the same format: HTML content
   * suitable for wrapping in a DIV.  This content should be preceded by 
   * an HTML &lt;script class="header"&gt;...&lt;script&gt; element
   * and the content should be followed by another similar 
   * &lt;script class="footer"&gt;...&lt;script&gt; element.  
   * These script elements must be the first and last HTML 
   * entities (not nested in another) and must be included even if empty.  
   * If not empty, each comment may contain only a single valid JSON object.
   * <p>  
   * The required and allowed fields of these contained JSON objects are 
   * documented in {@link com.googlecode.skald.client.commands.ResponseHeader}
   * and {@link com.googlecode.skald.client.commands.ResponseFooter}.
   * <p>
   * Processes header first, updating any GUI state.  Then handles footer,
   * again updating GUI state.  Finally passes response contents to GUI
   * to display.
   * 
   * @param text  Text sent in the body of the server's response
   */
  public static void handleResponse(String text) {

    HTML response = new HTML(text);
    Element elem = response.getElement();
   
   //confirm response is big enough for a header and footer
   int size = DOM.getChildCount(elem); 
    if (size < 2) {
      Skald.gui.setStatusError("Server sent an invalid response.");
      logger.log(Level.SEVERE, "Response is missing header/footer:\n" + elem);
      return;
    }

    //process header
    Element header = DOM.getChild(elem, 0);
    if (!header.getTagName().equals("SCRIPT") || 
        !header.getClassName().toLowerCase().equals("header")) {
      Skald.gui.setStatusError("Server sent an invalid response.");
      logger.log(Level.SEVERE, "Response is missing header:\n" + elem);
      return;      
    }
    handleHeader(header.getInnerText());
    
    //process footer
    Element footer = DOM.getChild(elem, size - 1);
    if (!footer.getTagName().equals("SCRIPT") || 
        !footer.getClassName().toLowerCase().equals("footer")) {
      Skald.gui.setStatusError("Server sent an invalid response.");
      logger.log(Level.SEVERE, "Response is missing footer:\n" + elem);
      return;      
    }
    handleFooter(footer.getInnerText());

    // remove header and footer now that processed
    elem.removeChild(header);
    elem.removeChild(footer);
    
    //display child
    Skald.gui.displayResponse(response.getHTML());
  }

  /**
   * Processes the internal JSON object that should be the sole contents
   * of a response footer.
   * 
   * @param json  Raw json text.
   */
  protected static void handleFooter(String json) {
    json = json.trim();
    logger.log(Level.FINER, "FOOTER: '" + json + "'");    
    if (!json.isEmpty()) {
      try {
        
        ResponseFooter footer = ResponseFooter.parse(json);
        Skald.gui.setAffordances(footer.getAffordances());
        Skald.gui.setObjects(footer.getObjects());
        
      }catch (IllegalArgumentException e) {
        Skald.gui.setStatusError("Server response contained invalid footer.");
        logger.log(Level.SEVERE, "Footer is invalid JSON.", e); //e gives json
        return;
      }catch (InvalidFormatException e) {
        Skald.gui.setStatusError("Server response footer contained " +
        		"invalid affordances.");
        logger.log(Level.SEVERE, "Affordances array is invalid: " + json, e);
        return;
      }
    }
  }

  /**
   * Processes the internal JSON object that should be the sole contents
   * of a response header.
   * 
   * @param json  Raw json text.
   */
  protected static void handleHeader(String json) {
    json = json.trim();
    logger.log(Level.FINER, "HEADER: '" + json + "'");  

    if (!json.isEmpty()) {
//TODO: process header
    }
  }

  /**
   * Sends a POST request to CMD_URL with the given cmd contents.
   * 
   * @see #sendRequest(Method, String)
   */
  public static void sendCmd(String cmd) {
    sendRequest(RequestBuilder.POST, CMD_URL, cmd);
  }
  
  /**
   * Sends a POST request to INIT_URL.
   * 
   * @see #sendRequest(Method, String)
   */
  public static void sendInit() {
    sendRequest(RequestBuilder.POST, INIT_URL, null);
  }
  
  /**
   * Sends a request of the given type (usually RequestBuilder.POST) to the
   * given URL.  Most Skald request should be POSTs because most will affect
   * a change in game state on the server and so are not idempotent.
   * <p>
   * This method will send the request and hand errors and responses through
   * the standard callbacks methods in this class: 
   * {@link #onError(Request, Throwable)} and 
   * {@link #onResponseReceived(Request, Response)}.
   *
   * @param method  type of request
   * @param url     full url to send request to
   */
  public static void sendRequest(Method method, String url, String data) {
    // Send request to server and catch any errors.
    RequestBuilder builder = new RequestBuilder(method, url);
    try {
      Skald.gui.setStatus("Connecting to server...");
      logger.log(Level.INFO, url + " " + method + ": " + data);
      builder.sendRequest(data, new Server());
    }catch (RequestException e) {
      String mesg = "Failed to connect to server.";
      logger.log(Level.SEVERE, mesg, e);
      Skald.gui.setStatusError(mesg);
    }
  }  
  
}
